package map;
//TODO: NEEDS IMPROVED COMMENTS
import java.awt.Image;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.imageio.ImageIO;

//TODO: MAKE AN EXCEPTION FOR TRYING TO ACCESS AN ENTRY THAT DOESN'T EXIST
//TODO: MAKE ^ THE EDGE DELIMETER
/**
 * Represents a library of terrain tiles and items which can be
 * placed on a map. A MapLibrary contains three things:<br>
 * - 1) A Collection of all MapLibraryTerrainEntries<br>
 * - 2) A Collection of all MapLibraryItemEntries<br>
 * - 3) A TreeMap that maps terrain edges to MapLibraryTerrainEntries that have that edge<br>
 * The MapLibrary class was constructed for use with the MapData class and can be used
 * in the quick construction of both random and user-generated maps.
 *
 * @author Alex Babis
 */
public class MapLibrary
{
	String name;
	TreeMap<Integer, MapLibraryTerrainEntry> terrainLibrary;
	TreeMap<Integer, MapLibraryItemEntry> itemLibrary;
	TreeMap<String, TreeSet<MapLibraryTerrainEntry>> edgeGroupings;
	
	/**
	 * Builds a MapLibrary with no entries
	 * @return An empty MapLibrary
	 */
	public MapLibrary()
	{
		terrainLibrary = new TreeMap<Integer, MapLibraryTerrainEntry>();
		itemLibrary = new TreeMap<Integer, MapLibraryItemEntry>();
		edgeGroupings = new TreeMap<String, TreeSet<MapLibraryTerrainEntry>>();
	}
	
	/**
	 * Builds a MapLibrary and constructs its Collections
	 * using Images in the specified folder and the specified
	 * settings file (also in the folder).
	 * The file must be plaintext and in the following format:<p>
	 * Terrain<br>
	 * 1 &nbsp; entry_name &nbsp; image.ext &nbsp; north_edge &nbsp; east_edge &nbsp; south_edge &nbsp; west_edge<br>
	 * 2 &nbsp; entry_name &nbsp; image.ext &nbsp; north_edge &nbsp; east_edge &nbsp; south_edge &nbsp; west_edge<br>
	 * ...<br>
	 * n &nbsp; entry_name &nbsp; image.ext &nbsp; north_edge &nbsp; east_edge &nbsp; south_edge &nbsp; west_edge<br>
	 * Items<br>
	 * 1 &nbsp; entry_name &nbsp; image.ext<br>
	 * 2 &nbsp; entry_name &nbsp; image.ext<br>
	 * ...<br>
	 * n &nbsp; entry_name &nbsp; image.ext<br>
	 * @param folder The folder in which the images and settings file are contained
	 * @param filename The filename of the settings file
	 * @return A MapLibrary with the entries specified by the settings file
	 * @see MapLibraryTerrainEntry, MapLibraryItemEntry
	 */
	public MapLibrary(String folder, String filename)
	{
		// TODO: MAKE A FILE CONSTRUCTION EXCEPTION FOR THIS CONSTRUCTOR
		terrainLibrary = new TreeMap<Integer, MapLibraryTerrainEntry>();
		itemLibrary = new TreeMap<Integer, MapLibraryItemEntry>();
		edgeGroupings = new TreeMap<String, TreeSet<MapLibraryTerrainEntry>>();
		
		// Open the settings file
		if(folder.charAt(folder.length() - 1) != File.separatorChar) 	// If last character isn't a backslash,
			folder = folder + File.separatorChar; 					// place one at the end
		name = folder;
		File file = new File(folder + filename).getAbsoluteFile();
		Scanner output = null;
		try {output = new Scanner(file);
		} catch (FileNotFoundException e) {e.printStackTrace();}
		
		// Process File
		String line = output.nextLine();
		if(!line.trim().toUpperCase().equals("TERRAIN"))
		{
			System.out.println("INVALID FILE CONSTRUCTION");
			return;
		}
		line = output.nextLine().trim();
		while(!line.trim().toUpperCase().equals("ITEMS"))
		{
			String[] split = line.split("[ \t]");

			int id = Integer.parseInt(split[0]); // Convert text to number
			
			Image image = null;
			try
			{image = ImageIO.read(new File(folder + split[2]));}
			catch (IOException e){}
			
			terrainLibrary.put(id,
					new MapLibraryTerrainEntry(id, split[1], image,
							split[3].split("_"), split[4].split("_"),
							split[5].split("_"), split[6].split("_")));
			
			//System.out.println(terrainLibrary.get(id));
			// Add this new entry to the edge groupings as well
			for(int i = 3; i <= 6; i++)
			{
				String[] edges = split[i].split("_");
				for(String edge : edges)
				{
					TreeSet<MapLibraryTerrainEntry> set = edgeGroupings.get(edge);
					if(set == null)
						set = new TreeSet<MapLibraryTerrainEntry>();
					set.add(terrainLibrary.get(id));
					edgeGroupings.put(edge, set);
				}
			}
			line = output.nextLine().trim();
		}
		while(output.hasNextLine())
		{
			line = output.nextLine().trim();
			String[] split = line.split("[ \t]");

			int id = Integer.parseInt(split[0]);
			
			Image image = null;
			try
			{image = ImageIO.read(new File(folder + split[2]));}
			catch (IOException e){}
			
			if(split.length > 3) // If size has been specified
				itemLibrary.put(id, new MapLibraryItemEntry(id, split[1], image,
						Integer.parseInt(split[3]), Integer.parseInt(split[4])));
			else
				itemLibrary.put(id, new MapLibraryItemEntry(id, split[1], image));
		}
	}
	
	/**
	 * Adds terrain entry to library
	 * @param entry The entry to be added to this library
	 */
	public void addTerrainEntry(MapLibraryTerrainEntry entry)
	{
		// Add to terrain library
		terrainLibrary.put(entry.getID(), entry);
		// Add to edge library
		for(String edge : entry.getEdges())
		{
			TreeSet<MapLibraryTerrainEntry> set = edgeGroupings.get(edge);
			if(set == null) set = new TreeSet<MapLibraryTerrainEntry>();
			set.add(entry);
			edgeGroupings.put(edge, set);
		}
	}
	
	/**
	 * Adds item entry to library
	 * @param entry The entry to be added to this library
	 */
	public void addItemEntry(MapLibraryItemEntry entry)
	{
		itemLibrary.put(entry.getID(), entry);
	}
	
	/** @return The MapLibraryTerrainEntry with the specified ID */
	public MapLibraryTerrainEntry getTerrainEntry(int id)
	{
		return terrainLibrary.get(id);
	}

	/** @return The MapLibraryItemEntry with the specified ID */
	public MapLibraryItemEntry getItemEntry(int id)
	{
		return itemLibrary.get(id);
	}
	
	/** @return A Set of all MapLibraryTerrainEntrys with the specified edge*/
	public Set<MapLibraryTerrainEntry> getEdgeGroupings(String edge)
	{
		TreeSet<MapLibraryTerrainEntry> set = edgeGroupings.get(edge);
		if(set != null)
			return new TreeSet<MapLibraryTerrainEntry>(edgeGroupings.get(edge));
		return null;
	}

	/** Gets a List of all MapLibraryTerrainEntrys in the library
	 * @return A List of all MapLibraryTerrainEntrys in the library*/
	public List<MapLibraryTerrainEntry> getTerrainList()
	{
		return new ArrayList<MapLibraryTerrainEntry>(terrainLibrary.values());
	}

	/** Gets a List of all MapLibraryItemEntrys in the library
	 * @return A List of all MapLibraryItemEntrys in the library*/
	public List<MapLibraryItemEntry> getItemList()
	{
		return new ArrayList<MapLibraryItemEntry>(itemLibrary.values());
	}

	/** Gets a List of all MapLibraryTerrainEntrys with the specified edge
	 * @return A List of all MapLibraryTerrainEntrys with the specified edge*/
	public List<MapLibraryTerrainEntry> getEdgeGroupingsList(String edge)
	{
		return new ArrayList<MapLibraryTerrainEntry>(edgeGroupings.get(edge));
	}

	/** Determines if two edge Strings may be legally adjacent on a map
	 * @return True if Strings are compatible; false otherwise*/
	public static boolean edgeStringsAreCompatible(String edge1, String edge2)
	{
		char lastChar1 = edge1.charAt(edge1.length() - 1);
		char lastChar2 = edge2.charAt(edge2.length() - 1);
		if(lastChar1 == 'o')
			return edge1.substring(0, edge1.length() - 1).equals(edge2);
		else if(lastChar2 == 'o')
			return edge2.substring(0, edge2.length() - 1).equals(edge1);
		switch(lastChar1)
		{
			case 'a':
				return edge2.equals(edge1.substring(0, edge1.length() - 1) + "b");
			case 'b':
				return edge2.equals(edge1.substring(0, edge1.length() - 1) + "a");
			default:
				return edge1.equals(edge2);
		}
	}
	
	public static List<String> getCompatibleEdges(String edge)
	{
		ArrayList<String> list = new ArrayList<String>();
		char lastChar = edge.charAt(edge.length() - 1);
		switch(lastChar)
		{
			case 'a':
				list.add(edge.substring(0, edge.length() - 1) + "b");
				break;
			case 'b':
				list.add(edge.substring(0, edge.length() - 1) + "a");
				break;
			case 'o':
				list.add(edge.substring(0, edge.length() - 1));
				break;
			default:
				list.add(edge);
				list.add(edge + "o");
				break;
		}
		return list;
	}

	public ArrayList<MapLibraryTerrainEntry> getPotentialAdjacentTiles(
			String edge) 
	{
		ArrayList<MapLibraryTerrainEntry> retList  = new ArrayList<MapLibraryTerrainEntry>();
		for(String otherEdge : getCompatibleEdges(edge))
		{
			Set<MapLibraryTerrainEntry> set = getEdgeGroupings(otherEdge);
			if(set != null)
				retList.addAll(set);
		}
		return retList;
	}

	public static boolean edgesAreCompatible(List<String> edges1,
			List<String> edges2) {
		for(String edge1 : edges1)
		{
			for(String edge2 : edges2)
			{
				if(edgeStringsAreCompatible(edge1, edge2))
					return true;
			}
		}
		return false;
	}

	/** Gets a List of all terrain with at least one edge that
	 * can legally be placed next to at least one of the given edges
	 * @param edges The edges that the terrain can be adjacent to
	 * @return A List of all terrain with at least one edge that
	 * can legally be placed next to at least one of the given edges
	 */
	public ArrayList<MapLibraryTerrainEntry> getPotentialAdjacentTiles(
			List<String> edges) {
		ArrayList<MapLibraryTerrainEntry> tiles = new ArrayList<MapLibraryTerrainEntry>();
		for(String edge : edges)
		{
			tiles.addAll(getPotentialAdjacentTiles(edge));
		}
		return tiles;
	}

	/**
	 * Gets the directory this library was built with
	 * @return The name of the directory this library was built with
	 */
	public String getName()
	{
		return name;
	}
}